package pay;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;


import JavaUtils.CSUtils.Log.CLog;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.client.methods.HttpPost;


import sun.misc.BASE64Decoder;

public class WxpayUtils {
    static final String tag = "WxpayUtils";
    String schema = "WECHATPAY2-SHA256-RSA2048";
    String mchid = "";

    /**
     * 构造签名
     *
     * @param params
     * @param encode
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
        Set<String> keysSet = params.keySet();
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        boolean first = true;
        for (Object key : keys) {
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            Object value = params.get(key);
            String valueString = "";
            if (null != value) {
                valueString = value.toString();
            }
            if (encode) {
                temp.append(URLEncoder.encode(valueString, "UTF-8"));
            } else {
                temp.append(valueString);
            }
        }
        return temp.toString();
    }

    public static Map<String, String> obejtToMap(Map<String, Object> tmpmap) {
        Map<String, String> map = new HashMap<String, String>();
        for (Entry<String, Object> ss : tmpmap.entrySet()) {
            if (ss.getKey().equals("sign")) {
                continue;
            }
            map.put(ss.getKey(), ss.getValue().toString());
        }
        return map;
    }

    // Authorization: <schema> <token>
    // GET - getToken("GET", httpurl, "")
    // POST - getToken("POST", httpurl, json)

    public static String getToken(String method, HttpPost url, String body, String mchid, String privateStr) {
        String nonceStr = UUID.randomUUID().toString().replace("-", "").toUpperCase();
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        CLog.i(tag,"message.getBytes().length" + message.getBytes().length + ">" + message);
        String signature = null;
        try {
            signature = sign(message.getBytes(StandardCharsets.UTF_8), privateStr);
            CLog.i(tag,"signature.getBytes().length" + message.getBytes().length);
        } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return "mchid=\"" + mchid + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + "2CAB86EF8A7B781ABBBCFC2E9EBD79EBA212B5EE" + "\","
                + "signature=\"" + signature + "\"";
    }

    static String sign(byte[] message, String privateStr)
            throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {

        Signature sign = Signature.getInstance("SHA256withRSA");
        //KeyPair ke =	createPKCS12("/home/cq/tjgame/apiclient_cert.p12", "TenPay Certificate", "1601286514");
        sign.initSign(getPrivateKey(privateStr));
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }


    private static KeyStore store;

    private final static Object lock = new Object();

    /**
     * 获取公私钥.
     *
     * @param keyPath  the key path
     * @param keyAlias the key alias
     * @param keyPass  password
     * @return the key pair
     */
    public static KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) {

        char[] pem = keyPass.toCharArray();
        try {
            FileInputStream fis = new FileInputStream(keyPath);
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(fis, pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
            certificate.checkValidity();
            // 证书的序列号 也有用
            String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
            // 证书的 公钥
            PublicKey publicKey = certificate.getPublicKey();
            // 证书的私钥
            PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);

            return new KeyPair(publicKey, storeKey);

        } catch (Exception e) {
            throw new IllegalStateException("Cannot load keys from store: " + keyPath, e);
        }
    }

    static String buildMessage(String method, HttpPost url, long timestamp, String nonceStr, String body) {

        String canonicalUrl = url.getURI().getRawPath();
        if (url.getURI().getRawQuery() != null) {
            canonicalUrl += "?" + url.getURI().getRawQuery();
        }

        return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
    }

    public static PrivateKey getPrivateKey(String privateStr) {
        PrivateKey parivatekey = null;
        PKCS8EncodedKeySpec priPkcs8 = null;
        try {
            priPkcs8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(privateStr));

            try {
                KeyFactory keyf = KeyFactory.getInstance("RSA");
                try {
                    parivatekey = keyf.generatePrivate(priPkcs8);
                } catch (InvalidKeySpecException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return parivatekey;
    }

    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    public static byte[] aesKey;

    public static String WxAPIV3AesUtil(byte[] key, byte[] associatedData, byte[] nonce, String ciphertext) {
        if (key.length != KEY_LENGTH_BYTE) {
            throw new IllegalArgumentException("无效的ApiV3Key，长度必须为32个字节");
        }
        aesKey = key;
        try {
            return decryptToString(associatedData, nonce, ciphertext);
        } catch (GeneralSecurityException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "";
    }

    public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);

            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 构造签名
     *
     * @param params
     * @param paternerKey
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String Sign(Map<String, String> params, String paternerKey) throws UnsupportedEncodingException {
        String string1 = createSign(params, false);
        String stringSignTemp = string1 + "&key=" + paternerKey;
        // String signValue = MD5Tools.encode(stringSignTemp).toUpperCase();
        String signValue = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
        return signValue;
    }

    public static void main(String[] args) {
        KeyPair ke = createPKCS12("D:\\newVersion\\1601286514_20201125_cert/apiclient_cert.p12", "TenPay Certificate", "1601286514");
        System.err.println(ke.getPrivate().getAlgorithm());
    }
}
